/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;

namespace CsJni
{
    public abstract class JniArray : JniObject
    {
        private const int JNI_COMMIT = 1;
        private const int JNI_ABORT = 2;

        protected internal JniArray(JniEnvironment environment, IntPtr handle, JniObjectLifecycle lifecycle)
            : base(environment, handle, lifecycle)
        {
        }

        internal static JniType ParseArrayPrimitiveType(string input)
        {
            switch (input)
            {
                case "Z": return JniType.Boolean;
                case "B": return JniType.Byte;
                case "C": return JniType.Char;
                case "D": return JniType.Double;
                case "F": return JniType.Float;
                case "I": return JniType.Int;
                case "J": return JniType.Long;
                case "S": return JniType.Short;
                default: return JniType.Null;
            }
        }

        public int GetArrayLength(
            JniEnvironment environment)
        {
            VerifyEnvironment(environment);
            int result = JniEnvironment.Functions.GetArrayLength(environment.Handle, this.Handle);
            JniThrowable.CheckException(environment);
            return result;
        }

        protected static void VerifyInputArray(Array buffer)
        {
            if (!(buffer != null && buffer.Length > 0))
            {
                throw new JniException(JniResourceManager.Instance.GetString("invalidArray"));
            }
        }

        protected internal void PackPrimitiveArray<T>(JniEnvironment environment, T[] sourceArray)
        {
            unsafe
            {
                bool isBoolType = typeof(T).Equals(typeof(bool));
                int typeSize = isBoolType ? 1 : Marshal.SizeOf(typeof(T));
                int length = sourceArray.Length;
                int byteCount = length * typeSize;
                byte[] byteArray = null;
                if (isBoolType)
                {
                    byteArray = JBooleanConverter.ToArray((bool[])(object)sourceArray);
                }
                else
                {
                    byteArray = new byte[byteCount];
                    Buffer.BlockCopy(sourceArray, 0, byteArray, 0, byteCount);
                }
                byte isCopy = JBooleanConverter.To(false);
                byte* pb = (byte*)JniEnvironment.Functions.GetPrimitiveArrayCritical(environment.Handle, this.Handle, &isCopy);
                if (pb == null)
                {
                    JniThrowable.ThrowCurrentException(environment);
                }
                try
                {
                    // IMPORTANT - do not change code between GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical.
                    // See JNI documentation.
                    Marshal.Copy(byteArray, 0, (new IntPtr(pb)), byteCount);
                }
                finally
                {   // last parameter 0 frees primitive array pointer and commits changes to the source Java Array
                    JniEnvironment.Functions.ReleasePrimitiveArrayCritical(environment.Handle, this.Handle, pb, 0);
                    // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
                    // not raise additional exceptions.
                }
            }
        }

        // OK to suppress
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected internal T[] ExtractPrimitiveArray<T>(JniEnvironment environment)
        {
            if (this.Handle == IntPtr.Zero) { return null; }
            unsafe
            {
                bool isBoolType = typeof(T).Equals(typeof(bool));
                int typeSize = isBoolType ? 1 : Marshal.SizeOf(typeof(T));
                int length = this.GetArrayLength(environment);
                int byteCount = length * typeSize;
                byte[] byteArray = new byte[byteCount];
                byte isCopy = JBooleanConverter.To(false);
                byte* pb = (byte*)JniEnvironment.Functions.GetPrimitiveArrayCritical(environment.Handle, this.Handle, &isCopy);
                if (pb == null)
                {
                    JniThrowable.ThrowCurrentException(environment);
                }
                try
                {
                    // IMPORTANT - do not change code between GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical.
                    // See JNI documentation.
                    Marshal.Copy((new IntPtr(pb)), byteArray, 0, byteCount);
                }
                finally
                {   // last parameter JNI_ABORT frees primitive array pointer without changing the source Java Array
                    JniEnvironment.Functions.ReleasePrimitiveArrayCritical(environment.Handle, this.Handle, pb, JNI_ABORT);
                    // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
                    // not raise additional exceptions.
                }
                T[] primitiveArray = new T[length];
                if (isBoolType)
                {
                    bool[] boolArray = JBooleanConverter.FromArray(byteArray);
                    Buffer.BlockCopy(boolArray, 0, primitiveArray, 0, byteCount);
                }
                else
                {
                    Buffer.BlockCopy(byteArray, 0, primitiveArray, 0, byteCount);
                }
                return primitiveArray;
            }
        }
    }
}
